﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Nine;
using Nine.Graphics;
using Nine.Graphics.Effects;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using PerformanceUtility.ShapeRendering;

namespace EventFlowLibrary.LevelComponents
{
    internal static class LevelSupport
    {
        /// <summary>
        /// Load Nine linked shadow effects.
        /// </summary>
        /// <param name="model"></param>
        /// <param name="effect"></param>
        /// <param name="contentManager"></param>
        /// <param name="lightView"></param>
        /// <param name="lightProjection"></param>
        internal static void LoadShadowEffect(Model model, string effect, ContentManager contentManager, Matrix _lightViewProjection)
        {
            LinkedEffect shadow = contentManager.Load<LinkedEffect>(effect);
            shadow.EnableDefaultLighting();

            // Light view and light projection defines a light frustum.
            // Shadows will be projected based on this frustom.
            //IEffectShadowMap shadowMapEffectPart = shadow.Find<IEffectShadowMap>();

            //// This value needs to be tweaked
            //shadowMapEffectPart.DepthBias = 0.005f;
            //shadowMapEffectPart.LightViewProjection = _lightViewProjection;
            //shadowMapEffectPart.ShadowColor = new Vector3(0.5f, 0.5f, 0.5f);

            //// If we don't specify a specular map, reduce it
            //IEffectMaterial materialEffect = shadow.Find<IEffectMaterial>();
            //if (!effect.Contains("Specular"))
            //{
            //    //materialEffect.SpecularPower = (255);
            //    //materialEffect.SpecularColor = new Vector3(0.1f, 0.1f, 0.1f);
            //    //materialEffect.SpecularPower = 2;
            //    //materialEffect.SpecularColor = new Vector3(0.00f, 0.00f, 0.00f);
            //}
            //else
            //{
            //    // MAke it very unglossy? TODO do this in game constructor and read from config
            //    //materialEffect.SpecularPower = (255);
            //    //materialEffect.SpecularColor = new Vector3(0.01f, 0.01f, 0.01f);
            //    //materialEffect.SpecularPower = 0;
            //    // TODO load specular... house has very high specular for some reason, track
            //    //materialEffect.SpecularColor = new Vector3(0.00000001f, 0.000000001f, 0.000000002f);
            //}
            //// temp
            //if (model.Tag == "house")
            //{
            //    //materialEffect.EmissiveColor = vec
            //    materialEffect.SpecularPower = 0;
            //    materialEffect.SpecularColor = new Vector3(0.0000000000f, 0.0000000000f, 0.0000000000f);
            //}
            //else
            //{
            //    //materialEffect.SpecularColor = Vector3.One * 10;
            //}

            // Set our amibient color. TODO match skybox TODO make shadow static so we don't get flickering.
            IAmbientLight ambientLightEffect = shadow.Find<IAmbientLight>();
            ambientLightEffect.AmbientLightColor = new Vector3(0.25f, 0.25f, 0.25f);

            IDirectionalLight directionalLight = shadow.Find<IDirectionalLight>();
            directionalLight.DiffuseColor = Vector3.One * .5f;// todo make this tmatch our skybox
            directionalLight.SpecularColor = Vector3.One * .5f;
            directionalLight.Direction = -Vector3.Normalize(new Vector3(60, 200, 20));// todo this is trying to match the offset of our shadow, make sure is correct. Tested: looks correct with shadows off, now tie to shadow direction.

            IPointLight pointLight = shadow.Find<IPointLight>();
            //pointLight.Range = 70;
            //pointLight.Attenuation = 3.5f;// higher the number the sharper the dropoff at the range
            //pointLight.SpecularColor = new Vector3(1, 0.8f, 0.8f);
            //pointLight.DiffuseColor = new Vector3(1, 0.8f, 0.8f);

            model.ConvertEffectTo(shadow);
        }

        /// <summary>
        /// Create a bounding box for the given model.
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        internal static BoundingBox CreateBoundingBox(Model model)
        {
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            // For each mesh of the model
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    // Vertex buffer parameters
                    int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                    int vertexBufferSize = meshPart.NumVertices * vertexStride;

                    // Get vertex data as float
                    float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                    meshPart.VertexBuffer.GetData<float>(vertexData);

                    // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                    for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                    {
                        Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), mesh.ParentBone.Transform);

                        min = Vector3.Min(min, transformedPosition);
                        max = Vector3.Max(max, transformedPosition);
                    }
                }
            }

            // Create and return bounding box
            return new BoundingBox(min, max);
        }

        /// <summary>
        /// Create a bounding box for the given model.
        /// </summary>
        /// <param name="model"></param>
        /// <param name="worldTransform"></param>
        /// <returns></returns>
        internal static BoundingBox CreateBoundingBox(Model model, Matrix worldTransform)
        {
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            // For each mesh of the model
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    // Vertex buffer parameters
                    int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                    int vertexBufferSize = meshPart.NumVertices * vertexStride;

                    // Get vertex data as float
                    float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                    meshPart.VertexBuffer.GetData<float>(vertexData);

                    // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                    for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                    {
                        Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), mesh.ParentBone.Transform * worldTransform);

                        min = Vector3.Min(min, transformedPosition);
                        max = Vector3.Max(max, transformedPosition);
                    }
                }
            }

            // Create and return bounding box
            return new BoundingBox(min, max);
        }

        /// <summary>
        /// Creates the WorldViewProjection matrix from the perspective of the 
        /// light using the cameras bounding frustum to determine what is visible 
        /// in the scene.
        /// </summary>
        /// <returns>The WorldViewProjection for the light</returns>
        internal static Matrix CreateLightViewProjectionMatrix(Vector3 _lightDir, BoundingFrustum _cameraFrustum)
        {
            // Matrix with that will rotate in points the direction of the light
            Matrix lightRotation = Matrix.CreateLookAt(Vector3.Zero,
                                                       -_lightDir,
                                                       Vector3.Up);

            // Get the corners of the frustum
            Vector3[] frustumCorners = _cameraFrustum.GetCorners();

            // Transform the positions of the corners into the direction of the light
            for (int i = 0; i < frustumCorners.Length; i++)
            {
                frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightRotation);
            }

            // Find the smallest box around the points
            BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners);
            Vector3 boxSize = lightBox.Max - lightBox.Min;
            Vector3 halfBoxSize = boxSize * 0.5f;

            // The position of the light should be in the center of the back
            // pannel of the box. 
            Vector3 lightPosition = lightBox.Min + halfBoxSize;
            lightPosition.Z = lightBox.Min.Z;

            // We need the position back in world coordinates so we transform 
            // the light position by the inverse of the lights rotation
            lightPosition = Vector3.Transform(lightPosition,
                                              Matrix.Invert(lightRotation));

            // Create the view matrix for the light
            Matrix lightView = Matrix.CreateLookAt(lightPosition,
                                                   lightPosition - _lightDir,
                                                   Vector3.Up);

            // Create the projection matrix for the light
            // The projection is orthographic since we are using a directional light
            Matrix lightProjection = Matrix.CreateOrthographic(boxSize.X, boxSize.Y,
                                                               -boxSize.Z, boxSize.Z);

            return lightView * lightProjection;
        }
    }
}
